﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using Com.PhilChuang.Pmvp.Ui.Views;
using Com.PhilChuang.Utils;

namespace Com.PhilChuang.Pmvp.Ui.Presenters
{
	public abstract class AbstractGenericPresenter<TView> : AbstractPresenter
		where TView : class, IView
	{
		#region Constants
		private const int TIME_TO_WAIT_FOR_THREADS_BEFORE_DISPOSE_MS = 5000;
		#endregion

		#region Instance Variables
		private readonly ManualResetEvent m_ViewLoadedMre = new ManualResetEvent (false);
		private readonly ManualResetEvent m_ViewShownMre = new ManualResetEvent (false);
		private bool m_RefreshedDataAndUpdatedToViewBeforeShown = false;
		protected bool m_IsReloadingDataOnViewLoaded = false;
		#endregion

		#region Properties
		public TView View { get; set; }

		private void SetView (TView view)
		{
			view.ThrowIfNull ("view");

			if (!ReferenceEquals (View, default (TView)))
				throw new ArgumentException ("Already registered a view");

			View = view;
			SubscribeToViewEvents ();
			OnViewRegistered ();
		}
		#endregion

		#region Constructors / Dispose / Init
		protected AbstractGenericPresenter (TView view)
		{
			SetView (view);
		}

		/// <summary>
		/// Disposes of held resources.
		/// Call base impl as the last step.
		/// </summary>
		protected override void OnDispose ()
		{
			if (!ReferenceEquals (View, default (TView)))
			{
				UnsubscribeFromViewEvents ();
				// TODO consider disposing view synchronously
				ThreadPool.QueueUserWorkItem (
					obj =>
						{
							m_WaitForAsyncThreadsToFinishMre.WaitOne (TIME_TO_WAIT_FOR_THREADS_BEFORE_DISPOSE_MS);
							View.Dispose ();
							View = default (TView);
						});
			}
			base.OnDispose ();
		}
		#endregion

		#region To implement
		/// <summary>
		/// Fires when the View is registered with the Presenter during Presenter instantiation.
		/// Call base impl as first step.
		/// </summary>
		protected virtual void OnViewRegistered ()
		{
			View.EnableAllControls (false);
		}

		/// <summary>
		/// Subscribe to View events after the View is registered during Presenter instantiation.
		/// Call base impl as the first step.
		/// </summary>
		protected virtual void SubscribeToViewEvents ()
		{
			View.Loaded += OnViewLoaded;
			View.Shown += OnViewShown;
			View.DoClose += OnDoClose;
			View.Closed += OnClosed;
			View.Unloaded += OnUnloaded;
		}

		/// <summary>
		/// Unsubscribes to View events after the View has Closed.
		/// Call base impl as the first step.
		/// </summary>
		protected virtual void UnsubscribeFromViewEvents ()
		{
			View.Unloaded -= OnUnloaded;
			View.Closed -= OnClosed;
			View.DoClose -= OnDoClose;
			View.Shown -= OnViewShown;
			View.Loaded -= OnViewLoaded;
		}

		/// <summary>
		/// Refreshes Presenter data from the Application.
		/// </summary>
		protected virtual void RefreshData ()
		{
			// does nothing
		}

		/// <summary>
		/// Updates the View with Presenter data.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void UpdateToView ()
		{
			// does nothing
		}

		/// <summary>
		/// Updates the Presenter with View data.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void UpdateFromView ()
		{
			// does nothing
		}
		#endregion

		#region IView Events
		/// <summary>
		/// Fires when the View has Loaded.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void OnViewLoaded ()
		{
			DebugWriteLine ("OnViewLoaded");
			if (ReturnToPresenter != null)
			{
				if (GetType () == ReturnToPresenter)
				{ // at the desired screen
					ReturnToPresenter = null;
				}
				else
				{ // not yet, keep going back
					View.Close ();
					return;
				}
			}
			m_ViewLoadedMre.Set ();
		}

		/// <summary>
		/// Fires every time the View is Shown.
		/// Base impl refreshes view data if that hasn't occurred yet.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void OnViewShown ()
		{
			DebugWriteLine ("OnViewShown");
			if (!m_IsReloadingDataOnViewLoaded)
			{
				if (!m_RefreshedDataAndUpdatedToViewBeforeShown)
					RefreshDataAndUpdateToView ();
				m_RefreshedDataAndUpdatedToViewBeforeShown = false;
				View.EnableAllControls (true);
			}
			m_ViewShownMre.Set ();
		}

		/// <summary>
		/// Fires when the View is trying to Close.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected virtual void OnDoClose (Object sender, CancelEventArgs e)
		{
			DebugWriteLine ("OnDoClose");
			// does nothing
		}

		/// <summary>
		/// Fires when the View is Closed.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void OnClosed ()
		{
			DebugWriteLine ("OnClosed");
			// does nothing
		}

		/// <summary>
		/// Fires when the View is Unloaded.
		/// Base impl calls Dispose.
		/// Call base impl as the last step.
		/// </summary>
		protected virtual void OnUnloaded ()
		{
			DebugWriteLine ("OnUnloaded");
			Dispose ();
		}
		#endregion

		#region Functionality
		public virtual void Show ()
		{
			RefreshDataAndUpdateToView ();
			View.Show ();
		}
		#endregion

		#region Private functionality
		/// <summary>
		/// Calls RefreshData then UpdateToView
		/// </summary>
		protected void RefreshDataAndUpdateToView ()
		{
			RefreshData ();
			UpdateToView ();
			m_RefreshedDataAndUpdatedToViewBeforeShown = true;
		}

		/// <summary>
		/// Threadblocks until the View has Loaded
		/// </summary>
		protected void WaitForViewLoaded ()
		{
			m_ViewLoadedMre.WaitOne ();
		}

		/// <summary>
		/// Threadblocks until the View is Shown
		/// </summary>
		protected void WaitForViewShown ()
		{
			m_ViewShownMre.WaitOne ();
		}
		#endregion
	}
}